package wheateClient

import (
	"bytes"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"gitee.com/timedb/wheatClient/etc"
	"github.com/gorilla/websocket"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path"
	"strings"
)

//初始化
func MustInit(path string) error {
	conf, err := etc.LoadConfig(path)
	if err != nil {
		return err
	}

	err = etc.SettingFileVerify(conf)
	if err != nil {
		return err
	}

	etc.SysConf = conf
	return nil

}

//客户端回复类
type WheatFile struct {
	file  *os.File //大文件
	buf   []byte   //小文件
	genre int      //文件类型
}

//关闭文件
func (w *WheatFile) Close() {
	w.buf = nil
	name := w.file.Name()

	w.file.Close()
	os.Remove(name)

}

//关闭文件
func (w *WheatFile) Save(filePath string) error {
	_, err := os.Stat(filePath)
	//不覆盖文件
	if err == nil {
		return AlreadyFileErr
	}
	//小文件
	f, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer f.Close()
	if w.genre == MinFileType { //小文件
		f.Write(w.buf)
		return nil
	} else { //大文件
		buf := make([]byte, 4096)
		w.file.Seek(0, os.SEEK_SET)
		for true {
			n, err := w.file.Read(buf)
			if err != nil {
				if err == io.EOF { //传输完毕
					return nil
				}
				return err
			}
			f.Write(buf[:n])
		}
	}
	return err
}

//客户端类
type Client struct {
	ip    string
	port  int
	host  string
	again int //重新测试次数
}

//新建客户端
func NewClient() (*Client, error) {
	if etc.SysConf == nil {
		return nil, NotInitConfErr
	}

	ip := etc.SysConf.GetTracker().Host
	port := etc.SysConf.GetTracker().Port

	return &Client{
		ip:    ip,
		port:  port,
		host:  fmt.Sprintf("%s:%d", ip, port),
		again: 3,
	}, nil

}

func (c *Client) getStorageHost() (string, error) {

	url := fmt.Sprintf("http://%s%s", c.host, GetIpApi)

	for i := 0; i < c.again; i++ {
		resp, err := http.Get(url)
		if err == nil && resp.StatusCode == 200 {
			host, err := ioutil.ReadAll(resp.Body)
			if err == nil {
				resp.Body.Close()
				f := new(getAddr)
				json.Unmarshal(host, f)
				return f.Addr, nil
			}
			resp.Body.Close()
		}

	}

	return "", TrackerErr
}

//大文件返回false
func calculateHash(file multipart.File) (bool, []byte, error) {
	buf := make([]byte, 32*1024)
	size := 0
	hash := ""

	for true {
		n, err := file.Read(buf)
		if err != nil {
			if err == io.EOF {
				//读取完毕
				break
			}
			return true, nil, err
		}

		size += n
		hash += fmt.Sprintf("%x", sha256.Sum256(buf[:n]))
	}

	//判断大小小于50mb
	if size < 1024*1024*50 {
		file.Seek(0, os.SEEK_SET)
		buf, err := ioutil.ReadAll(file)
		if err != nil {
			return true, nil, err
		}
		hash = fmt.Sprintf("%x", sha256.Sum256(buf))

		return true, []byte(hash), nil //返回小文件hash

	} else {
		hash = fmt.Sprintf("%x", sha256.Sum256([]byte(hash)))
		return false, []byte(hash), nil //返回大文件hash
	}

}

//上传文件统一接口
func (c *Client) UploadFile(file multipart.File, name string) (string, error) {
	isMinFile, hash, err := calculateHash(file)
	if err != nil { //文件hash失败
		return "", err
	}

	for i := 0; i < c.again; i++ {
		if isMinFile {
			key, err := c.upMinFile(file, hash, name)
			if err != nil { //重复
				continue
			}

			return key, nil

		} else {
			key, err := c.upMaxFile(file, hash, name)
			if err != nil { //重复
				continue
			}

			return key, nil
		}
	}

	return "", FileUploadErr
}

//验证令牌是否存在
func (c *Client) VerKey(key string) (bool, string) {
	url := fmt.Sprintf("http://%s/%s?hash=%s", c.host, ExaHashApi, key)
	for i := 0; i < c.again; i++ {
		resp, err := http.Get(url)
		if err == nil {

			addr, _ := ioutil.ReadAll(resp.Body)

			resp.Body.Close()
			if resp.StatusCode == 200 {
				return true, string(addr)
			} else if resp.StatusCode == 406 {
				return false, ""
			}

		}

	}

	return false, ""

}

//上传大文件接口
func (c *Client) upMaxFile(file multipart.File, hash []byte, name string) (string, error) {

	//验证令牌
	key := fmt.Sprintf("max:group/%s/%s/%s%s", hash[60:62], hash[62:64], hash[0:32], path.Ext(name))
	flag, _ := c.VerKey(key)

	if flag {
		return key, nil
	}

	host, err := c.getStorageHost()
	if err != nil {
		return "", err
	}

	url := fmt.Sprintf("ws://%s%s?hash=%s&name=%s", host, UpLoadMaxFileWcApi, string(hash), path.Ext(name))
	d := websocket.DefaultDialer //默认客户端
	ws, resp, err := d.Dial(url, nil)
	if err != nil {
		return "", err
	}

	defer ws.Close()
	defer resp.Body.Close()

	//发送文件
	file.Seek(0, os.SEEK_SET) //文件头开始
	buf := make([]byte, 32*1024)
	for true {
		n, err := file.Read(buf) //读取文件
		if err != nil {
			//文件传输完毕
			if err == io.EOF {
				ws.WriteMessage(websocket.TextMessage, []byte("ok"))
				break
			}

			//传输失败
			return "", err

		}
		//发送
		ws.WriteMessage(websocket.TextMessage, buf[:n])
	}

	//校验
	m, _, err := ws.ReadMessage()
	if err != nil {
		return "", err
	}

	//保存成功
	if m == websocket.CloseNormalClosure {
		return key, nil
	}

	return "", FileUploadErr

}

//上传小文件接口
func (c *Client) upMinFile(file multipart.File, hash []byte, name string) (string, error) {

	//获取一个负载服务器
	hosts, err := c.getStorageHost()
	if err != nil {
		return "", err
	}
	s := string(hash)
	key := fmt.Sprintf("group:%s:path:%s/%s:%s%s", "group", s[60:62], s[62:64], s[0:32], path.Ext(name))

	//令牌存在，使用秒传
	flag, key := c.VerKey(key)

	if flag {
		return key, nil
	}

	//发送文件请求部分，构建文件发送请求

	file.Seek(0, os.SEEK_SET)
	client := http.Client{}
	bodyBuf := &bytes.Buffer{}
	bodyWrite := multipart.NewWriter(bodyBuf)

	fileWrite, err := bodyWrite.CreateFormFile("file", name)

	if err != nil {
		return "", err
	}
	_, err = io.Copy(fileWrite, file) //拷贝消息
	if err != nil {
		return "", err
	}

	bodyWrite.Close() //关闭写对象

	contentType := bodyWrite.FormDataContentType()

	url := fmt.Sprintf("http://%s%s", hosts, UpLoadMinFilePostApi)

	req, err := http.NewRequest(http.MethodPost, url, bodyBuf)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", contentType)
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	} else if resp.StatusCode != 200 {
		return "", RequestErr
	}

	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	val := new(getKey)
	err = json.Unmarshal(b, val)
	if err != nil {
		return "", err
	}

	return val.Key, nil

}

//统一下载文件接口
func (c *Client) GetFile(key string) (*WheatFile, string, error) {
	//获取一个storage接口

	fl, addr := c.VerKey(key)
	//不存在
	if !fl {
		return nil, "", TrackerNotHaveTheKeyErr
	}

	storageHost, err := c.getStorageHost()
	if err != nil {
		return nil, "", err
	}

	//大文件令牌
	if key[:3] == "max" {
		f, name, err := c.getMaxFile(key, addr)
		if err != nil {
			return nil, "", err
		}
		return f, name, nil

	} else { //小文件

		f, name, err := c.getMinFile(key, storageHost)
		if err != nil {
			return nil, "", DownLoadFileErr
		}

		return f, name, nil

	}

}

//下载小文件
func (c *Client) getMinFile(key string, host string) (*WheatFile, string, error) {

	url := fmt.Sprintf("http://%s%s?key=%s", host, GetMinFileApi, key)

	//失败重试c.again
	for i := 0; i < c.again; i++ {
		resp, err := http.Get(url)
		if err != nil {
			continue
		}

		//请求成功
		if resp.StatusCode == 200 {
			name := resp.Header.Get("Content-Disposition")
			name = strings.Replace(name, "filename=", "", 1) //去掉文件名描述
			buf, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				resp.Body.Close()
				continue
			}
			resp.Body.Close()

			//访问令牌
			f := new(WheatFile)
			f.buf = buf
			f.genre = MinFileType

			return f, name, nil

		}

	}

	return nil, "", DownLoadFileErr

}

func (c *Client) getMaxFile(key string, host string) (*WheatFile, string, error) {

	name := fmt.Sprintf("%s.bat", key[22:38])

	//创建缓存区
	_f, err := os.Create(fmt.Sprintf("%s/%s", etc.SysConf.Client.CachePath, name))
	if err != nil {
		return nil, "", err
	}

	url := fmt.Sprintf("http://%s%s?key=%s", host, GetMaxFileApi, key)
	for i := 0; i < c.again; i++ {
		resp, err := http.Get(url)
		if err != nil {
			continue
		}

		//请求成功
		if resp.StatusCode == 200 {
			name := resp.Header.Get("Content-Disposition")[33:]
			io.Copy(_f, resp.Body)

			resp.Body.Close()

			//访问令牌
			f := new(WheatFile)
			f.file = _f
			f.genre = MaxFileType

			return f, name, nil

		}

	}

	_f.Close()

	return nil, "", nil
}
